package org.uninova.mobis.api.interfaces.open.routers;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

import org.uninova.mobis.utils.SysUtilsImpl;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * RoutinoInterface
 * @author PAF
 */
public class RoutinoInterface {

	/**
	 * RoutinoInterface class constructor
	 */
	public RoutinoInterface() {}
	
	/**
	 * This program reads in the OSM format XML file and splits it up to create the database that is used for routing.
	 * 
	 * @param directory			Sets the directory name in which to save the results. Defaults to the current directory.
	 * @param prefix			Sets the filename prefix for the files that are created. Defaults to no prefix.
	 * @param ramSize			Specifies the amount of RAM (in MB) to use for sorting the data. If not specified then 64 MB will be used in slim mode or 256 MB otherwise.
	 * @param threads			The number of threads to use for data sorting (the sorting memory is shared between the threads - too many threads and not enough memory will reduce the performance).
	 * @param parseOnly			Parse the input files and store the data in intermediate files but don't process the data into a routing database. This option must be used with the --append option for all except the first file.
	 * @param processOnly		Don't read in any files but process the existing intermediate files created by using the --parse-only option.
	 * @param keep				Store a set of intermediate files after parsing the OSM files, sorting and removing duplicates; this allows appending an OSC file and re-processing later.
	 * @param append			Parse the input file and append the result to the existing intermediate files; the appended file can be either an OSM file or an OSC change file.
	 * @param changes			This option indicates that the data being processed contains one or more OSC (OSM changes) files, they must be applied in time sequence if more than one is used. This option implies --append when parsing data files and --keep when processing data.
	 * @param pruneIsolated		Remove the access permissions for a transport type from small disconnected groups of segments and remove the segments if they nd up with no access permission (defaults to removing groups under 500m).
	 * @param pruneShort		Remove short segments (defaults to removing segments up to a maximum length of 5m).
	 * @param pruneStraight		Remove nodes in almost straight highways (defaults to removing nodes up to 3m offset from a straight line).
	 * @param filename			Specifies the filename(s) to read data from. Filenames ending '.pbf' will be read as PBF, filenames ending in '.o5m' or '.o5c' will be read as O5M/O5C, otherwise as XML. Filenames ending '.bz2' will be bzip2 uncompressed (if bzip2 support compiled in). Filenames ending '.gz' will be gzip uncompressed (if gzip support compiled in).
	 */
	public void initPlanetSplitter(String directory, 
								   String prefix, 
								   int ramSize, 
								   int threads,
								   boolean parseOnly, 
								   boolean processOnly, 
								   boolean keep, 
								   boolean append,
								   boolean changes,
								   int pruneIsolated, 
								   int pruneShort, 
								   int pruneStraight, 
								   String filename) {
		
		String command = "planetsplitter --dir=" + directory + " --prefix=" + prefix ;
		
		if (ramSize > 0) {
			command = command.concat(" --sort-ram-size=" + ramSize) ;
		}
		if (threads > 0) {
			command = command.concat(" --sort-threads=" + threads) ;
		}
		if (parseOnly) {
			command = command.concat(" --parse-only") ;
		}
		if (processOnly) {
			command = command.concat(" --process-only") ;
		}
		if (keep) {
			command = command.concat(" --keep") ;
		}
		if (append) {
			command = command.concat(" --append") ;
		}
		if (changes) {
			command = command.concat(" --changes") ;
		}
		
		if (pruneIsolated > 0) {
			command = command.concat(" --prune-isolated=" + pruneIsolated) ;
		}
		else if (pruneShort > 0) {
			command = command.concat(" --prune-short=" + pruneShort) ;
		}
		else if (pruneStraight > 0) {
			command = command.concat(" --prune-straight=" + pruneStraight) ;
		}
		else {
			command = command.concat(" --prune-none") ;
		}
		
		command = command.concat(" " + filename) ;
		String[] comms = command.split(" ") ;
		SysUtilsImpl sysUtils = new SysUtilsImpl() ;
		
		String result = sysUtils.executeInShell(comms, false, false) ;
		System.out.println(result) ;
	}
	
	/**
	 * This program performs the calculation of the optimum routes using the database generated by the planetsplitter program.
	 * 
	 * @param directory			Sets the directory name in which to read the local database. Defaults to the current directory.
	 * @param prefix			Sets the filename prefix for the files in the local database. Defaults to no prefix.
	 * @param profiles			Sets the filename containing the list of routing profiles in XML format. If the file doesn't exist then dirname, prefix and "profiles.xml" will be combined and used, if that doesn't exist then the file '/usr/local/share/routino/profiles.xml' (or custom installation location) will be used.	
	 * @param translations		Sets the filename containing the list of translations in XML format for the output files. If the file doesn't exist then dirname, prefix and "translations.xml" will be combined and used, if that doesn't exist then the file '/usr/local/share/routino/translations.xml' (or custom installation location) will be used.
	 * @param exactNodesOnly	When processing the specified latitude and longitude points only select the nearest node instead of finding the nearest point within a segment (quicker but less accurate unless the points are already near nodes).
	 * @param loggable			Print progress messages that are suitable for logging to a file; normally an incrementing counter is printed which is more suitable for real-time display than logging.
	 * @param quiet				Don't generate any screen output while running (useful for running in a script).
	 * @param language			Select the language specified from the file of translations. If this option is not given and the file exists then the first language in the file will be used. If this option is not given and no file exists the compiled-in default language (English) will be used.
	 * @param profile			Specifies the name of the profile to use.
	 * @param transport			Select the type of transport to use, <transport> can be set to:
	 *								- foot = Foot
	 *								- horse = Horse
	 *								- wheelchair = Wheelchair
	 *								- bicycle = Bicycle
	 *								- moped = Moped (Small motorcycle, limited speed)
	 *								- motorcycle = Motorcycle
	 *								- motorcar = Motorcar
	 *								- goods = Goods (Small lorry, van)
	 *								- hgv = HGV (Heavy Goods Vehicle - large lorry)
	 *								- psv = PSV (Public Service Vehicle - bus, coach)
	 *							Defaults to 'motorcar', this option also selects the default profile information if the '--profile' option is not given and a profile matching the transport name is found.
	 * @param shortest			True: Find the shortest route between the waypoints. False: Find the quickest route between the waypoints.
	 * @param lats				The location of the waypoints that make up the start, middle and end points of the route. Up to 99 waypoints can be specified and the route will pass through each of the specified ones in sequence. 
	 * @param lngs				The algorithm will use the closest node or point within a segment that allows the specified traffic type.
	 * @param highwayPrefs		Selects the percentage preference for using each particular type of highway. The value of <highway> can be selected from:
	 *								- motorway = Motorway
	 *								- trunk = Trunk
	 *								- primary = Primary
	 *								- secondary = Secondary
	 *								- tertiary = Tertiary
	 *								- unclassified = Unclassified
	 *								- residential = Residential
	 *								- service = Service
	 *								- track = Track
	 *								- cycleway = Cycleway
	 *								- path = Path
	 *								- steps = Steps
	 *								- ferry = Ferry
	 *							Default value depends on the profile selected by the --transport option.
	 * @param speedPrefs		Selects the speed limit in km/hour for each type of highway. Default value depends on the profile selected by the --transport option.
	 * @param propertyPrefs		Selects the percentage preference for using each particular highway property The value of <property> can be selected from:
	 *								- paved = Paved (suitable for normal wheels)
	 *								- multilane = Multiple lanes
	 *								- bridge = Bridge
	 *								- tunnel = Tunnel
	 *								- footroute = A route marked for foot travel
	 *								- bicycleroute = A route marked for bicycle travel
	 *							Default value depends on the profile selected by the --transport option.
	 * @param oneway			Selects if the direction of oneway streets are to be obeyed (useful to not obey them when walking). Default value depends on the profile selected by the --transport option.
	 * @param turns				Selects if turn restrictions are to be obeyed (useful to not obey them when walking). Default value depends on the profile selected by the --transport option.
	 * @param weight			Specifies the weight of the mode of transport in tonnes; ensures that the weight limit on the highway is not exceeded. Default value depends on the profile selected by the --transport option.
	 * @param height			Specifies the height of the mode of transport in metres; ensures that the height limit on the highway is not exceeded. Default value depends on the profile selected by the --transport option.
	 * @param width				Specifies the width of the mode of transport in metres; ensures that the width limit on the highway is not exceeded. Default value depends on the profile selected by the --transport option.
	 * @param length			Specifies the length of the mode of transport in metres; ensures that the length limit on the highway is not exceeded. Default value depends on the profile selected by the --transport option.
	 */
	public String initRoutinoRouter(String directory, 					// --dir=/home/paf/routino_data
								  String prefix, 
								  String profiles, 
								  String translations, 
								  boolean exactNodesOnly, 
								  boolean loggable, 
								  boolean quiet, 
								  String language, 
								  String profile, 
								  String transport, 
								  boolean shortest, 
								  ArrayList<Double> lats,
								  ArrayList<Double> lngs,
								  HashMap<String, Integer> highwayPrefs, 
								  HashMap<String, Integer> speedPrefs, 
								  HashMap<String, Integer> propertyPrefs, 
								  boolean oneway, 
								  boolean turns, 
								  double weight, 
								  double height, 
								  double width, 
								  double length) {
		
		String command = "routino-router --dir=" + directory + " --prefix=" + prefix + " --output-html --output-gpx-track --loggable" ;
		
		if (profiles != null && !profiles.equals("")) {
			command = command.concat(" --profiles=" + profiles) ;
		}
		if (translations != null && !translations.equals("")) {
			command = command.concat(" --translations=" + translations) ;
		}
		if (exactNodesOnly) {
			command = command.concat(" --exact-nodes-only") ;
		}
		/*if (loggable) {
			command = command.concat(" --loggable") ;
		}*/
		if (quiet) {
			command = command.concat(" --quiet") ;
		}
		if (language != null && !language.equals("")) {
			command = command.concat(" --language=" + language) ;
		}
		if (profile != null && !profile.equals("")) {
			command = command.concat(" --profile=" + profile) ;
		}
		if (transport != null && !transport.equals("")) {
			command = command.concat(" --transport=" + transport) ;
		}
		
		if (shortest) {
			command = command.concat(" --shortest") ;
		}
		else {
			command = command.concat(" --quickest") ;
		}
		
		if (!lats.isEmpty() && !lngs.isEmpty() && lats.size() == lngs.size()) {
			for (int i = 0; i <  lats.size(); i++) {
				command = command.concat(" --lat" + (i + 1) + "=" + lats.get(i) + " --lon" + (i + 1) + "=" + lngs.get(i)) ;
			}
		}
		
		String key ;
		Iterator<String> iter ;
		
		if (highwayPrefs != null && !highwayPrefs.isEmpty()) {
			iter = highwayPrefs.keySet().iterator() ;
			
			while (iter.hasNext()) {
				key = iter.next() ;
				command = command.concat(" --highway-" + key + "=" + highwayPrefs.get(key)) ;
			}
		}
		
		if (speedPrefs != null && !speedPrefs.isEmpty()) {	
			iter = speedPrefs.keySet().iterator() ;
			
			while (iter.hasNext()) {
				key = iter.next() ;
				command = command.concat(" --speed-" + key + "=" + speedPrefs.get(key)) ;
			}
		}
		
		if (propertyPrefs != null && !propertyPrefs.isEmpty()) {
			iter = propertyPrefs.keySet().iterator() ;
			while (iter.hasNext()) {
				key = iter.next() ;
				command = command.concat(" --property-" + key + "=" + propertyPrefs.get(key)) ;
			}
		}
		if (!transport.equals("foot")) {
			if (oneway) {
				command = command.concat(" --oneway=1") ;
			}
			else {
				command = command.concat(" --oneway=0") ;
			}
			
			if (turns) {
				command = command.concat(" --turns=1") ;
			}
			else {
				command = command.concat(" --turns=0") ;
			}
		}
		
		if (weight > 0.0) {
			command = command.concat(" --weight=" + weight) ;
		}
		
		if (height > 0.0) {
			command = command.concat(" --height=" + height) ;
		}
		
		if (width > 0.0) {
			command = command.concat(" --width=" + width) ;
		}
		
		if (length > 0.0) {
			command = command.concat(" --length=" + length) ;
		}
		
		String[] comms = command.split(" ") ;
		
		SysUtilsImpl sysUtils = new SysUtilsImpl() ;
		
		String result = sysUtils.executeInShell(comms, true, shortest) ;
		
		return result ;
	}
	
	/**
	 * Reads the instructions file in HTML created by Routino into a {@link java.lang.String} object
	 * @param shortest	<code>true</code> if the instructions correspond to the shortest route; <code>false</code> if the instructions correspond to the quickest route
	 * @return			The {@link java.lang.String} object with the HTML instructions
	 */
	public String readRouttinoInstructions(boolean shortest) {
		File file ;
		if (shortest)
			file = new File("shortest.html") ;
		else
			file = new File("quickest.html") ;
		
		String instructions = "" ;
		FileInputStream fstream;
		try {
			fstream = new FileInputStream(file);
			DataInputStream in = new DataInputStream(fstream) ;
			BufferedReader br = new BufferedReader(new InputStreamReader(in)) ;
			String strLine ;
			
			while ((strLine = br.readLine()) != null) {
				instructions += strLine ;
			}
			
			instructions = instructions.substring(instructions.indexOf("</H1>") + 5) ;
			instructions = instructions.substring(0, instructions.lastIndexOf("<p>")) ;
			
			fstream.close() ;
			in.close() ;
			br.close() ;
			file.delete() ;

		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return instructions ;
	}
	
	/**
	 * Updates a JSON {java.lang.String} object with an array of coordinates that represent the route from the result of Routino Router
	 * @param routinoResult	The result from the Routino Router
	 * @param jsonResponse	The JSON {java.lang.String} object to update
	 * @return				The updated JSON {@link java.lang.String} object
	 */
	public String generateJSONCoordinatesResponse(String routinoResult, String jsonResponse, String delimiter) {
		SysUtilsImpl sysUtils = new SysUtilsImpl() ;
		try {
			Document doc = sysUtils.loadXMLFromString(routinoResult);
			
			doc.getDocumentElement().normalize() ;
			
			String lat, lng ;
			
			NodeList nList = doc.getElementsByTagName("trkpt") ;
			for (int i = 0; i < nList.getLength(); i++) {
				Node n = nList.item(i) ;
				if (n.getNodeType() == Node.ELEMENT_NODE) {
					Element e = (Element) n ;
					lat = e.getAttribute("lat") ;
					lng = e.getAttribute("lon") ;
					jsonResponse += ("[" + lat + "," + lng + "]" + delimiter) ;
				}
			}
			jsonResponse = jsonResponse.substring(0, jsonResponse.lastIndexOf(delimiter)) ;
		} catch (Exception e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		return jsonResponse ;
	}
}
